package cn.keniykey.mc.landterritory.landflag;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import javax.annotation.Nullable;

import com.google.common.io.ByteStreams;

import cn.keniykey.mc.landterritory.landflag.api.JavaLandFlag;
import cn.keniykey.mc.landterritory.toolkit.DebugUtil;

public final class LandFlagClassLoader extends URLClassLoader{
	private final ConcurrentHashMap<String,Class<?>> conmap_classes;
	private final URL url;
	private final JarFile jarfile;
	private final Manifest manifest;
	private final JavaLandFlag flag;
	
	public LandFlagClassLoader(File flagfile,LandFlagDescription description,@Nullable ClassLoader parent) throws Exception {
		super(new URL[]{flagfile.toURI().toURL()}, parent);
		this.conmap_classes = new ConcurrentHashMap<>();
		this.url = flagfile.toURI().toURL();
		Class<?> jarclass;
		String main = description.getMain();
		try {
			this.jarfile = new JarFile(flagfile);
		}catch(IOException ex) {
			throw new Exception("领地标记Class加载[LandFlagClassLoader]：无法将File转换为JarFile", ex);
		}
		this.manifest = this.jarfile.getManifest();
		try {
			jarclass = Class.forName(main, true, this);
		}catch(ClassNotFoundException ex) {
			this.jarfile.close();
			throw new Exception("领地标记Class加载[LandFlagClassLoader]：找不到Main: " + main, ex);
		}
		Class<? extends JavaLandFlag> flagclass;
		try {
			flagclass = jarclass.asSubclass(JavaLandFlag.class);
		}catch(ClassCastException ex) {
			this.jarfile.close();
			throw new Exception("领地标记Class加载[LandFlagClassLoader]：目标Main不是JavaLandFlag：" + main , ex);
		}
		try {
			this.flag = (JavaLandFlag) flagclass.newInstance();
		} catch (InstantiationException | IllegalAccessException ex) {
			this.jarfile.close();
			throw new Exception("领地标记Class加载[LandFlagClassLoader]：目标Main无法创建对象", ex);
		}
		((LandFlag)this.flag).init(description, this);
	}
	
	public JavaLandFlag getFlag() {
		return this.flag;
	}
	@Override
	protected Class<?> findClass(final String name) throws ClassNotFoundException {
		return this.findClass(name, true);
	}
	Class<?> findClass(final String name,boolean checkloader) throws ClassNotFoundException{
		if (name.startsWith("cn.keniykey.mc.landterritory") || name.startsWith("cn.keniykey.mc.library") ||
				name.startsWith("org.bukkit") || name.startsWith("net.minecraft")) {
			throw new ClassNotFoundException(name);
		}
		Class<?> find = this.conmap_classes.get(name);
		if (find == null) {
			if (checkloader) find = LandFlagLoader.getFlagClass(name);
			if (find == null) {
				final String path = name.replace('.', '/').concat(".class");
				final JarEntry en;
				try {
					en = this.jarfile.getJarEntry(path);
				}catch(Exception ex) {
					DebugUtil.info("领地标记寻找Class[LandFlagClassLoader]：获取获取到JarEntry");
					throw new ClassNotFoundException(name, ex);
				}
				if (en == null) return find;
				byte[] classbytes;
				try {
					final InputStream input = this.jarfile.getInputStream(en);
					classbytes = ByteStreams.toByteArray(input);
				}catch(IOException e) {
					throw new ClassNotFoundException(name,e);
				}
				final int dot = name.lastIndexOf(46);
				if (dot != -1) {
					final String pagename = name.substring(0,dot);
					if (this.getPackage(pagename) == null) {
						try {
							if (this.manifest != null) {
								this.definePackage(pagename, this.manifest, this.url);
							}else {
								this.definePackage(pagename, null, null, null, null, null, null, null);
							}
						}catch(IllegalArgumentException ex) {
							if (this.getPackage(pagename) == null) throw new ClassNotFoundException(pagename, ex);
						}
					}
				}
				final CodeSource source = new CodeSource(this.url, en.getCodeSigners());
				find = this.defineClass(name, classbytes, 0, classbytes.length, source);
			}
			if (find == null) find = super.findClass(name);
			if (find != null) LandFlagLoader.setClass(name, find);
		}
		this.conmap_classes.put(name, find);
		return find;
	}
	Set<String> getClasses() {
		return this.conmap_classes.keySet();
	}
	@Override
	public void close() throws IOException {
		try {
			LandFlagLoader.removeLandFlagClassLoader(this);
			LandFlagLoader.removeBufferClass(this.conmap_classes);
			this.conmap_classes.clear();
			super.close();
		}finally {
			try {
				if (this.jarfile != null) this.jarfile.close();
			}catch(Exception ex) {}
		}
	}
}